#define FORCE_TYPE(t, v) (*reinterpret_cast<t*>(&v))

#include <stdio.h>
#include <iostream>
#include <thread>
#include <mutex>
#include <string>
#include <chrono>

#include "serialport.h"
#include "logger_plus_test_lib.h"

#ifdef OS_WIN
#include <Windows.h>
#include <fileapi.h>
#include <tchar.h>
#endif

#ifdef OS_WIN
std::string getWinSerialPorts()
{
    // https://msdn.microsoft.com/en-us/library/ms724256

#define MAX_KEY_LENGTH 255
#define MAX_VALUE_NAME 16383

    HKEY hKey = nullptr;

    TCHAR achValue[MAX_VALUE_NAME]; // buffer for subkey name
    DWORD cchValue = MAX_VALUE_NAME; // size of name string
    TCHAR achClass[MAX_PATH] = _T(""); // buffer for class name
    DWORD cchClassName = MAX_PATH; // size of class string
    DWORD cSubKeys = 0; // number of subkeys
    DWORD cbMaxSubKey; // longest subkey size
    DWORD cchMaxClass; // longest class string
    DWORD cKeyNum; // number of values for key
    DWORD cchMaxValue; // longest value name
    DWORD cbMaxValueData; // longest value data
    DWORD cbSecurityDescriptor; // size of security descriptor
    FILETIME ftLastWriteTime; // last write time

    int iRet = -1;
    std::vector<std::string> portsName;
    TCHAR strDSName[MAX_VALUE_NAME];
    memset(strDSName, 0, MAX_VALUE_NAME);
    // DWORD nValueType = 0;
    DWORD nBuffLen = 10;

    // open registry key and get information
    if (ERROR_SUCCESS == RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_READ, &hKey)) {
        // Get the class name and the value count.
        iRet = RegQueryInfoKeyA(
            hKey, // key handle
            achClass, // buffer for class name
            &cchClassName, // size of class string
            NULL, // reserved
            &cSubKeys, // number of subkeys
            &cbMaxSubKey, // longest subkey size
            &cchMaxClass, // longest class string
            &cKeyNum, // number of values for this key
            &cchMaxValue, // longest value name
            &cbMaxValueData, // longest value data
            &cbSecurityDescriptor, // security descriptor
            &ftLastWriteTime); // last write time

        // Enumerate the key values.
        if (cKeyNum > 0 && ERROR_SUCCESS == iRet) {
            for (int i = 0; i < (int)cKeyNum; i++) {
                cchValue = MAX_VALUE_NAME;
                achValue[0] = '\0';
                nBuffLen = MAX_KEY_LENGTH; // 防止 ERROR_MORE_DATA 234L 错误
                if (ERROR_SUCCESS == RegEnumValueA(hKey, i, achValue, &cchValue, NULL, NULL, (LPBYTE)strDSName, &nBuffLen)) {
                    portsName.emplace_back(std::string(strDSName));
                }
            }
        }
    }

    if (hKey)
        RegCloseKey(hKey);

    std::string ret;
    if (portsName.empty()) {
        ret = "[]";
    } else {
        ret = fmt::format("{}", portsName);
    }
    return ret;
}
#endif

void SerialPort::getAvailableSerialPorts(char* toBuf)
{
    if (!toBuf)
        return;
    std::string ports;
#ifdef OS_WIN
    ports = getWinSerialPorts();
#endif
    auto len = ports.length();
    std::memcpy(toBuf, ports.c_str(), len);
    toBuf[len] = 0;
}

SerialPort::SerialPort()
{
    m_openPortFailed = false;
    m_portName = new char[256];
    m_portHandle = nullptr;
    m_lastErr = 0;
    setDefaultParams();
}

SerialPort::SerialPort(const SerialPort& obj)
{
    this->m_baudRate = obj.m_baudRate;
    this->m_parity = obj.m_parity;
    this->m_dataBits = obj.m_dataBits;
    this->m_stopBits = obj.m_stopBits;
    this->m_flowControl = obj.m_flowControl;
    this->m_readBufSize = obj.m_readBufSize;
    this->m_writeBufSize = obj.m_writeBufSize;
    this->m_baudRate = obj.m_baudRate;
    for (int i = 0; i < 255; ++i) {
        this->m_portName[i] = obj.m_portName[i];
        if (obj.m_portName[i] == 0)
            break;
    }
    this->m_portName[255] = 0;
}

SerialPort& SerialPort::operator=(const SerialPort& obj)
{
    this->m_baudRate = obj.m_baudRate;
    this->m_parity = obj.m_parity;
    this->m_dataBits = obj.m_dataBits;
    this->m_stopBits = obj.m_stopBits;
    this->m_flowControl = obj.m_flowControl;
    this->m_readBufSize = obj.m_readBufSize;
    this->m_writeBufSize = obj.m_writeBufSize;
    this->m_baudRate = obj.m_baudRate;
    for (int i = 0; i < 255; ++i) {
        this->m_portName[i] = obj.m_portName[i];
        if (obj.m_portName[i] == 0)
            break;
    }
    this->m_portName[255] = 0;

    return *this;
}

SerialPort::~SerialPort()
{
    if (isOpen()) {
        close();
    }

    if (m_portName) {
        delete[] m_portName;
        m_portName = nullptr;
    }
}

bool SerialPort::open()
{
    if (isOpen()) {
        SPDLOG_WARN("Serial Port is opened!");
        return true;
    }

#ifdef OS_WIN

    if (m_portHandle)
        CloseHandle(m_portHandle);

    //    GetLastError();
    SetLastError(ERROR_SUCCESS);
    auto portname = fmt::format("\\\\.\\{}", m_portName);

    HANDLE h = CreateFileA(portname.c_str(), GENERIC_READ | GENERIC_WRITE,
        0, //   指定共享属性，由于串口不能共享，所以该参数必须为0
        nullptr,
        OPEN_EXISTING, //    打开而不是创建
        FILE_ATTRIBUTE_NORMAL, //    属性描述，该值为FILE_FLAG_OVERLAPPED，表示使用异步I/O，该参数为0，表示同步I/O操作
        nullptr);

    if (h == INVALID_HANDLE_VALUE) {
        m_portHandle = nullptr;
        DWORD& err = *reinterpret_cast<DWORD*>(&m_lastErr);
        err = GetLastError();
        if (!m_openPortFailed) {
            m_openPortFailed = true;
            std::string s;
            s.resize(2048);
            getAvailableSerialPorts(&s[0]); // c++11起std::string使用连续内存
            SPDLOG_ERROR("Open Serialport '{}' failed(ErrorCode={:#010x})! AvailableSerialPorts={}",
                m_portName,
                m_lastErr,
                &s[0]);
        }
    } else {
        m_portHandle = static_cast<void*>(h);
        m_openPortFailed = false;

        COMMPROP prop;
        prop.wPacketLength = sizeof(prop);
        if (GetCommProperties(m_portHandle, &prop)) {
            m_readBufSize = prop.dwMaxRxQueue;
            m_writeBufSize = prop.dwMaxTxQueue;
        }

        if (!updateParamToSerialPort()) {
            SPDLOG_ERROR("Port Parameters Set Failed!");
            DWORD& err = *reinterpret_cast<DWORD*>(&m_lastErr);
            err = GetLastError();
        }

        SetupComm(m_portHandle, m_readBufSize, m_writeBufSize);

        setTimeoutParams(-1, 0, 0, 0, 0);

        clear();
    }
#endif
    return m_portHandle != nullptr;
}

bool SerialPort::isOpen()
{
    return m_portHandle != nullptr;
}

void SerialPort::close()
{
    if (isOpen()) {
#ifdef OS_WIN
        if (m_portHandle)
            CloseHandle(m_portHandle);
#endif
        m_portHandle = nullptr;
        m_openPortFailed = false;
    }
}

void SerialPort::setPortName(const char* name)
{
    if (!name)
        return;
    for (int i = 0; i < 255; ++i) {
        if (name[i] == 0)
            break;
        m_portName[i] = name[i];
    }
    m_portName[255] = 0;
}

bool SerialPort::setBaudRate(int baudRate)
{
    m_baudRate = baudRate;
    return updateParamToSerialPort();
}

bool SerialPort::setParity(SerialPort::Parity parity)
{
    m_parity = parity;
    return updateParamToSerialPort();
}

bool SerialPort::setDataBits(SerialPort::DataBits dataBits)
{
    m_dataBits = dataBits;
    return updateParamToSerialPort();
}

bool SerialPort::setStopBits(SerialPort::StopBits stopBits)
{
    m_stopBits = stopBits;
    return updateParamToSerialPort();
}

bool SerialPort::setFlowControl(SerialPort::FlowControl flowControl)
{
    m_flowControl = flowControl;
    return updateParamToSerialPort();
}

#ifdef OS_WIN
bool SerialPort::setTimeoutParams(int rInterval, int rMultiple, int rConst, int wMultiple, int wConst)
{
    if (isOpen()) {
        COMMTIMEOUTS TimeOuts; // 全部单位都为ms
        TimeOuts.ReadIntervalTimeout = FORCE_TYPE(DWORD, rInterval); //    读间隔超时         两个字符间接收时间起过这个值会使read返回  当为0xFFFFFFFF时表示立即返回
        TimeOuts.ReadTotalTimeoutMultiplier = FORCE_TYPE(DWORD, rMultiple); //    读时间系数    读取时长超过 指定字符数乘以这个值时 read返回
        TimeOuts.ReadTotalTimeoutConstant = FORCE_TYPE(DWORD, rConst); //    读时间常量     每次读取的固有超时长度
        TimeOuts.WriteTotalTimeoutMultiplier = FORCE_TYPE(DWORD, wMultiple); //   写时间系数     写入时长超过 指定字符数乘以这个值时 write返回
        TimeOuts.WriteTotalTimeoutConstant = FORCE_TYPE(DWORD, wConst); //  写时间常量       每次写入的固有超时长度
        return (SetCommTimeouts(m_portHandle, &TimeOuts) != 0);
    }
    return false;
}
#endif

void SerialPort::setReadBufferSize(unsigned int size)
{
    m_readBufSize = size;
#ifdef OS_WIN
    if (isOpen()) {
        SetupComm(m_portHandle, m_readBufSize, m_writeBufSize);
    }
#endif
}

void SerialPort::setWriteBufferSize(unsigned int size)
{
    m_writeBufSize = size;

    if (isOpen()) {
#ifdef OS_WIN
        SetupComm(m_portHandle, m_readBufSize, m_writeBufSize);
#endif
    }
}

bool SerialPort::flush()
{
    if (isOpen()) {
#ifdef OS_WIN
        return (FlushFileBuffers(m_portHandle) != 0);
#endif
    }

    return false;
}

bool SerialPort::clear()
{
    if (isOpen()) {
#ifdef OS_WIN
        return (PurgeComm(m_portHandle,
                    PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR)
            != 0);
#endif
    }

    return false;
}

bool SerialPort::waitForReadyRead(int timeout_ms)
{
    if (isOpen() && timeout_ms > 0) {
#ifdef OS_WIN
        COMSTAT ComStat;
        DWORD dwErrorFlags;
        timeout_ms += 9;
        timeout_ms = timeout_ms / 10;
        for (int i = 0; i < timeout_ms; ++i) {
            ClearCommError(m_portHandle, &dwErrorFlags, &ComStat);
            if (ComStat.cbInQue > 0)
                break;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }

        return ComStat.cbInQue > 0;
#endif
    } else {
        return false;
    }
}

int SerialPort::read(void* buf, int bytesToRead)
{
    if (!isOpen())
        return -1;
#ifdef OS_WIN
    DWORD rs = 0;
    getLastError();
    bool res = (ReadFile(m_portHandle, buf, FORCE_TYPE(DWORD, bytesToRead), &rs, nullptr)
        != 0);
    if (!res) {
        m_lastErr = getLastError();
        if (m_lastErr != ERROR_IO_PENDING && m_lastErr != ERROR_SUCCESS) {
            clearSerialPortError();
            close();
            SPDLOG_ERROR("Serial Port read() not pending error! : {:#010x}\nSerial Port Closed!");
        }
        return -1;
    }
    return static_cast<int>(rs);
#endif
}

int SerialPort::write(void* buf, int bytesToWrite)
{
    return this->write(reinterpret_cast<const void*>(buf), bytesToWrite);
}

int SerialPort::write(const void* buf, int bytesToWrite)
{
    if (!isOpen())
        return -1;
#ifdef OS_WIN
    DWORD ws = 0;
    getLastError();
    bool res = (WriteFile(m_portHandle, buf, FORCE_TYPE(DWORD, bytesToWrite), &ws, nullptr)
        != 0);
    if (!res) {
        m_lastErr = getLastError();
        if (m_lastErr != ERROR_IO_PENDING && m_lastErr != ERROR_SUCCESS) {
            clearSerialPortError();
            close();
            SPDLOG_ERROR("Serial Port write() not pending error! : {:#010x}\nSerial Port Closed!");
        }
        return -1;
    }
    return static_cast<int>(ws);
#endif
}

int SerialPort::getLastError()
{
    return m_lastErr;
}

void SerialPort::clearSerialPortError()
{
    if (isOpen()) {
#ifdef OS_WIN
        COMSTAT ComStat;
        DWORD dwErrorFlags;
        ClearCommError(m_portHandle, &dwErrorFlags, &ComStat);
//        ComStat.cbInQue;    //    这个是在缓冲区接收到的数据长度
#endif
    }
}

void SerialPort::setDefaultParams()
{
#ifdef OS_WIN
    const char name[] = "COM1";

#endif

#ifdef OS_LINUX
    const char name[] = "/dev/ttyS0";
#endif

    for (size_t i = 0; i < sizeof(name); ++i) {
        m_portName[i] = name[i];
    }
    m_portName[sizeof(name)] = 0;

    m_baudRate = Baud19200;
    m_parity = NoParity;
    m_dataBits = Data8;
    m_stopBits = OneStop;
    m_flowControl = NoFlowControl;

    m_readBufSize = 1024;
    m_writeBufSize = 1024;
}

bool SerialPort::updateParamToSerialPort()
{
    if (!isOpen())
        return true;

#ifdef OS_WIN
    DCB dcb;
    GetCommState(static_cast<HANDLE>(m_portHandle), &dcb);
    dcb.BaudRate = static_cast<unsigned long>(m_baudRate);
    switch (m_parity) {
    case EvenParity:
        dcb.Parity = EVENPARITY;
        break;
    case OddParity:
        dcb.Parity = ODDPARITY;
        break;
    case SpaceParity:
        dcb.Parity = SPACEPARITY;
        break;
    case MarkParity:
        dcb.Parity = MARKPARITY;
        break;
        //    case UnknownParity:
        //    case NoParity:
    default:
        dcb.Parity = NOPARITY;
        break;
    }

    switch (m_dataBits) {
    case Data5:
        dcb.ByteSize = 5;
        break;
    case Data6:
        dcb.ByteSize = 6;
        break;
    case Data7:
        dcb.ByteSize = 7;
        break;
    case Data8:
        dcb.ByteSize = 8;
        break;
        //    case UnknownDataBits:
    default:
        dcb.ByteSize = 8;
        break;
    }

    switch (m_stopBits) {
    case OneStop:
        dcb.StopBits = ONESTOPBIT;
        break;
    case OneAndHalfStop:
        dcb.StopBits = ONE5STOPBITS;
        break;
    case TwoStop:
        dcb.StopBits = TWOSTOPBITS;
        break;
        //    case UnknownStopBits:
    default:
        dcb.StopBits = ONESTOPBIT;
        break;
    }

    switch (m_flowControl) {
    case HardwareControl:
        dcb.fOutxCtsFlow = TRUE;
        dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
        break;
    case SoftwareControl:
        dcb.fOutX = TRUE; // XON/XOFF out flow control
        dcb.fInX = TRUE; // XON/XOFF in flow control
        dcb.XonLim = static_cast<WORD>(m_readBufSize * 0.25f); // transmit XON threshold
        dcb.XoffLim = static_cast<WORD>(m_readBufSize * 0.75f); // transmit XOFF threshold
        dcb.XonChar = 17; // Tx and Rx XON character    ctrl+q
        dcb.XoffChar = 19; // Tx and Rx XOFF character ctrl+s
        break;
    default:
        dcb.fOutxCtsFlow = FALSE;
        dcb.fOutxDsrFlow = FALSE;
        dcb.fDtrControl = DTR_CONTROL_DISABLE;
        dcb.fRtsControl = RTS_CONTROL_DISABLE;
        break;
    }

    bool res = (SetCommState(m_portHandle, &dcb) != 0);
    if (!res)
        m_lastErr = getLastError();
    return res;

#endif
}
